/*********************************************************************************
 *      Copyright:  (C) 2025 iot25<lingyun@email.com>
 *                  All rights reserved.
 *
 *       Filename:  client_connect_sever.c
 *    Description:  This file client_connect_sever.c
 *                 
 *        Version:  1.0.0(01/05/25)
 *         Author:  iot25 <lingyun@email.com>
 *      ChangeLog:  1, Release initial version on "01/05/25 00:24:55"
 *                 
 ********************************************************************************/
#include<stdio.h>
#include<error.h>
#include<string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>

#include"client_connect_sever.h"
#include"logger.h"

#define BUF_SIZE 500
#define CONNECT_RETRY_TIMES 3
#define CONNECT_RETRY_DELAY_MS 200
#define CONNECT_TIMEOUT_SEC    3    

int socket_init(socket_t *sock, char *hostname, int port)
{
	if( !sock || !hostname || (port < 0) )
	{
		log_error("invalid_argument!\n");
		return -1;
	}

	memset(sock, 0, sizeof(socket_t));

	strncpy(sock->host, hostname, sizeof(sock->host)-1);
	sock->port = port;
	sock->fd = -1;
	log_debug("Socket initialized successfully: host=%s, port=%d\n", sock->host, sock->port);
	return 0;
}

int get_addr_info(const char *host, const char *port, struct addrinfo **res)
{
	struct addrinfo 		hints;
	int						status;

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM; /* Datagram socket */
    hints.ai_flags = 0;
    hints.ai_protocol = IPPROTO_TCP;

	status = getaddrinfo(host, port, &hints, res);
	if (status != 0) 
	{
		log_error("getaddrinfo [%s:%s] failed: %s\n", host, port, gai_strerror(status));
        return -1;
	}
	return 0;

}

int client_connect_server(socket_t *sock)
{
    char 					port_str[16] = {0};
    int 					retries = 0;
    int 					status = -1;
    struct addrinfo 		*res = NULL;
    struct addrinfo 		*ip_buf = NULL;
	struct addrinfo			hints;
    char 					ip_str[INET6_ADDRSTRLEN] = {0}; 
    fd_set 					writefds;
    int 					select_ret;
    int 					so_error;
//    socklen_t	 			len;
    void					*addr_ptr = NULL;
	const char 				*ip_ver;
	struct sockaddr_in 		*ipv4 = NULL;
	struct sockaddr_in6 	*ipv6 = NULL;
	int						flag = 0;
	struct timeval 			tv;

    // 参数检查
    if (NULL == sock || sock->port <= 0)
	{
        log_error("Invalid socket parameters\n");
        return -1;
    }
    // 清理旧连接
    if (sock->fd >= 0) 
	{
        close(sock->fd);
        sock->fd = -1;
    }
    sock->connected = 0;

    snprintf(port_str, sizeof(port_str), "%d", sock->port);

	memset(&hints, 0, sizeof(hints));

    while (retries < CONNECT_RETRY_TIMES) 
	{
        log_debug("Connection attempt %d/%d\n", retries + 1, CONNECT_RETRY_TIMES);

        // 获取地址信息
        if (get_addr_info(sock->host, port_str, &res) != 0) 
		{
			retries++;
            continue;
        }

        // 遍历所有地址（IPv4/IPv6）
        for (ip_buf = res; ip_buf != NULL; ip_buf = ip_buf->ai_next) 
		{
            // 清理旧 Sockt
            if (sock->fd >= 0) 
			{
                close(sock->fd);
                sock->fd = -1;
            }


            // 转换为可读地址字符串（支持 IPv4/IPv6）
            if (ip_buf->ai_family == AF_INET) 
			{  // IPv4
                ipv4 = (struct sockaddr_in*)ip_buf->ai_addr;	//转换成ipv4的结构体
                addr_ptr = &(ipv4->sin_addr);					//获取到地址存储在addr_ptr中
                ip_ver = "IPv4";
				inet_ntop(AF_INET, addr_ptr, ip_str, sizeof(ip_str));
            } 
			else 
			{  // IPv6
                ipv6 = (struct sockaddr_in6 *)ip_buf->ai_addr;
                addr_ptr = &(ipv6->sin6_addr);
                ip_ver = "IPv6";
				inet_ntop(AF_INET6, addr_ptr, ip_str, sizeof(ip_str));
            }
            log_debug("Trying %s address: %s:%d\n", ip_ver, ip_str, sock->port);

			//creat_socket
			sock->fd = socket(ip_buf->ai_family, ip_buf->ai_socktype, ip_buf->ai_protocol);
            if (sock->fd < 0)
            {
                log_error("socket() failed: %s\n", strerror(errno));
                continue;
            }

			// 设置为非阻塞模式
            flag = fcntl(sock->fd, F_GETFL, 0);
            if (flag < 0 || fcntl(sock->fd, F_SETFL, flag | O_NONBLOCK) < 0) {
                perror("fcntl() failed");
                close(sock->fd);
                continue;
            }

            // 尝试连接（使用 ai_addrlen 确保地址长度正确）
            status = connect(sock->fd, ip_buf->ai_addr, ip_buf->ai_addrlen);
            if (status == 0)
			{
                log_debug("Immediate %s connection success\n", ip_ver);
                goto connection_success;
            }

            // 处理非阻塞连接
            if (errno == EINPROGRESS) 
			{
                FD_ZERO(&writefds);
                FD_SET(sock->fd, &writefds);

                tv.tv_sec = CONNECT_TIMEOUT_SEC;
                tv.tv_usec = 0;

                select_ret = select(sock->fd + 1, NULL, &writefds, NULL, &tv);
                if (select_ret > 0 && FD_ISSET(sock->fd, &writefds)) 
				{
                    socklen_t len = sizeof(so_error);
                    getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, &so_error, &len);
                    if (so_error == 0) 
					{
                        printf("Connected to %s:%d\n", ip_str, sock->port);
                        goto connection_success;
                    }
					else
					{
						 log_warn("Connection failed: %s\n", so_error ? strerror(so_error) : "getsockopt failed\n");
						 close(sock->fd);
						 sock->fd = -1;
						 continue;
					}
                }
			}
		}	

        // 清理并重试
        freeaddrinfo(res);
        res = NULL;

        retries++;
        if (retries < CONNECT_RETRY_TIMES) /*在重连次数限制下重连*/
		{
            log_error("Connection attempt %d/%d failed, retrying...\n", retries, CONNECT_RETRY_TIMES);
            usleep(CONNECT_RETRY_DELAY_MS * 1000);
        }
    }

    log_error("Failed to connect to %s:%d after %d attempts\n", sock->host, sock->port, CONNECT_RETRY_TIMES);
    goto cleanup;

connection_success:
    //将标志位变为1，方便判断是否连上
    sock->connected = 1;

    // 清理资源
    if (res != NULL) 
	{
        freeaddrinfo(res);
        res = NULL;
    }
    return 0;

cleanup:
    // 统一清理
    if (res != NULL) 
	{
        freeaddrinfo(res);
        res = NULL;
    }
    if (sock->fd >= 0) 
	{
        close(sock->fd);
        sock->fd = -1;
    }
    sock->connected = 0;
    return -1;
}

int socket_close(socket_t *sock)
{
    if ( NULL == sock )
    {
        log_error("NULL pointer\n");
        return 0;
    }
    if (sock->fd >= 0)
    {
        close(sock->fd);
        sock->fd = -1;
    }
    return 1;
}
